package com.lwy.setter;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <br/>
 *
 * @author bukong
 * @date 2022/8/8
 */
@Slf4j
public class NameSetterMeta {

    static final Map<Class<?>, BaseNameSetter<?>> ANNOTATION_SETTER_MAP = new ConcurrentHashMap<>();
    /**
     * DTO-注解-字段 元数据信息
     * key1: dto class,
     * Key2: annotation class,
     * key3: annotated field,
     * value: field pointed by annotation
     */
    private static final Map<Class<?>, Map<Class, Map<Field, Field>>> FIELD_MAP_CACHE = new ConcurrentHashMap<>();

    public static boolean isClassNeedProcess(Class<?> clz) {
        return FIELD_MAP_CACHE.containsKey(clz);
    }

    /**
     * 获取DTO类型中拥有的注解类型
     *
     * @param objClass DTO类
     * @return 注解类型
     */
    public static List<Class> getAnnotationClasses(Class objClass) {
        if (!FIELD_MAP_CACHE.containsKey(objClass)) {
            return Collections.emptyList();
        }
        return new ArrayList(FIELD_MAP_CACHE.get(objClass).keySet());
    }

    /**
     * 查询DTO类中被指定注解标注的属性映射信息
     *
     * @param objClz        目标对象
     * @param annotationClz 目标注解
     * @param <T>           目标对象类型
     * @return 字段映射关系
     */
    public static <T, A extends Annotation> Map<Field, Field> getFieldMapper(Class<T> objClz, Class<A> annotationClz) {
        if (FIELD_MAP_CACHE.containsKey(objClz) && FIELD_MAP_CACHE.get(objClz).containsKey(annotationClz)) {
            return FIELD_MAP_CACHE.get(objClz).get(annotationClz);
        }
        return parseAnnotatedFields(objClz, annotationClz);
    }

    /**
     * 解析DTO类中被对应注解类所标注的属性映射关系
     *
     * @param objClz        DTO类
     * @param annotationClz 注解类
     * @param <T>           DTO类型
     * @param <A>           注解类型
     * @return 属性映射
     */
    public static synchronized <T, A extends Annotation> Map<Field, Field> parseAnnotatedFields(Class<T> objClz, Class<A> annotationClz) {
        try {
            final Map<Class, Map<Field, Field>> annotationFieldMap = FIELD_MAP_CACHE.getOrDefault(objClz, new ConcurrentHashMap<>());
            // double check
            if (annotationFieldMap.containsKey(annotationClz)) {
                return annotationFieldMap.get(annotationClz);
            }
            Method annotationValueMethod = null;
            try {
                annotationValueMethod = annotationClz.getDeclaredMethod("value");
//                annotationNameFieldMethod = annotationClz.getDeclaredMethod("nameField");
            }catch (NoSuchMethodException e) {
                log.warn("Annotation [{}] can't find Method [value()] or [nameField()] : {}", annotationClz.getName(), e.getMessage());
            }
            Map<Field, Field> fieldMapper = new HashMap<>();
            Field[] fields =  getAllField(objClz);
            for (Field field : fields) {
                if (field.isAnnotationPresent(annotationClz)) {
                    final A annotation = field.getAnnotation(annotationClz);
                    field.setAccessible(true);

                        String nameFieldVal = Objects.toString(annotationValueMethod.invoke(annotation));
//                        if(StringUtils.isBlank(nameFieldVal) && annotationNameFieldMethod != null){
//                            nameFieldVal = Objects.toString(annotationNameFieldMethod.invoke(annotation));
//                        }
                        if(StringUtils.isNotBlank(nameFieldVal)) {
                            try {
                            final Field nameField = field.getDeclaringClass()
                                    .getDeclaredField(nameFieldVal);
                            nameField.setAccessible(true);
                            fieldMapper.put(field, nameField);
                            }catch (NoSuchFieldException e) {
                                log.error("NameSetterMeta 未能解析到配置的属性，[{}#{}配置的被填充字段为{}]", annotationClz.getName(), field.getName(), nameFieldVal);
                            }
                        }else{
                            log.warn("NameSetterMeta 未能解析到正确的配置，[{}#{}]", annotationClz.getName(), field.getName());
                        }

                }
            }
            annotationFieldMap.put(annotationClz, fieldMapper);
            FIELD_MAP_CACHE.put(objClz, annotationFieldMap);
            return fieldMapper;
        } catch (InvocationTargetException | IllegalAccessException e) {
            // ignore
        }
        return Collections.emptyMap();
    }

    private static <T> Field[] getAllField(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        return fieldList.toArray(fields);
    }

    /**
     * 注册dto类和注解类的映射关系
     *
     * @param dClass dto类
     * @param aClass 注解类
     * @param <D>    dto类型
     * @param <A>    注解类型
     */
    public static <D, A extends Annotation> void bindTypeAnnotation(Class<D> dClass, Class<A> aClass) {
        NameSetterMeta.parseAnnotatedFields(dClass, aClass);
    }

    static <T extends Annotation> void registerNameSetter(Class<T> annotationClass, BaseNameSetter<T> setter) {
        ANNOTATION_SETTER_MAP.put(annotationClass, setter);
        log.info("填充器[{}]注册成功", annotationClass.getName());
    }


}
